#include "DLM_Manager.h"

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <set>

#define PIPE_READ 0
#define PIPE_WRITE 1

void clean_up_client_init();
void critical(const char * msg);
void server_loop();
void process_lock(pid_t pid, int resource_id, int lock_type, long timeout);
void process_unlock(pid_t pid, int resource_id);
void process_trylock(pid_t pid, int resource_id, int lock_type);
void process_timeout(pid_t pid, int resource_id);
void write_response(pid_t pid, char return_value);
void process_read_buff(char* buff, pid_t* pid, char* flag, int* resource_id, int* lock_type, long* timeout);
void *timeout_thread(void *ptr);
int get_client_descriptor(pid_t pid);

//server - descriptor map
std::map<pid_t, int> * client_descriptors_map;
std::vector<pid_t> pid_vector; //FIXME: zmieniłem na zwracanie nie przez referencję a przez wartosc więc pewnie tą zmienną można usunąć
std::set<pid_t> * ignore_timout_set;

//client/server - common pipe
int common_pipe;

//server global mutex
pthread_mutex_t global_mutex = PTHREAD_MUTEX_INITIALIZER;

//client - pipe for client
int client_pipe;

//server - DLM_Manager
DLM_Manager * dlm_manager;

int DLM_init(int client_num)
{
	client_descriptors_map = new std::map<pid_t, int>();

	int common_pipes[2];	// communication from client to server
	int status=pipe(common_pipes);
	if (status == -1)
	{
		critical("common_pipe");
		return DLM_INIT_FAIL_STATUS;
	}

	for(int i = 0; i < client_num; ++i) {
		int client_pipes[2]; //comunication form server to client
		int status=pipe(client_pipes);
		if (status == -1)
		{
			critical("client_pipe");
			return DLM_INIT_FAIL_STATUS;
		}

		pid_t ret;
		if((ret = fork()) < 0) //error
		{
			critical("fork");
			return DLM_INIT_FAIL_STATUS;
		}

		if(ret == 0) //child section
		{
			bool last_client = (i == client_num - 1);	
			//close unused pipes
			close(common_pipes[PIPE_READ]);
			if(!last_client)
			{
				close(client_pipes[PIPE_WRITE]);
				clean_up_client_init();
			}

			//save common pipe descriptor
			common_pipe = common_pipes[PIPE_WRITE];

			//save client read pipe descriptor
			client_pipe = client_pipes[PIPE_READ];

			return last_client ? DLM_INIT_OK_COORDINATOR_STATUS : DLM_INIT_OK_CLIENT_STATUS;
		}
		else //server section
		{
			//close unused pipes
			close(client_pipes[PIPE_READ]);

			//add client descriptor to map
			(*client_descriptors_map)[ret] = client_pipes[PIPE_WRITE];
		}
	}

	//server pipes
	common_pipe = common_pipes[PIPE_READ];
	close(common_pipes[PIPE_WRITE]);

	dlm_manager = new DLM_Manager();

	ignore_timout_set = new std::set<pid_t>();

	server_loop();

	delete ignore_timout_set;
	delete dlm_manager;
	delete client_descriptors_map;

	exit(0);
}

void server_loop()
{
	char buff[CLIENT_SERVER_PACKET_LENGTH];
	pid_t pid;
	int resource_id, lock_type;
	long timeout;
	char flag;

	while(read(common_pipe, buff, CLIENT_SERVER_PACKET_LENGTH) == CLIENT_SERVER_PACKET_LENGTH)
	{
		pthread_mutex_lock(&global_mutex);

		process_read_buff(buff, &pid, &flag, &resource_id, &lock_type, &timeout);

		switch(flag)
		{
		case MSG_FLAG_LOCK:
			printf("Processing lock %d, %d, %d, %d, %ld\n", pid, flag, resource_id, lock_type, timeout);
			process_lock(pid, resource_id, lock_type, timeout);
			break;
		case MSG_FLAG_UNLOCK:
			printf("Processing unlock %d, %d, %d, %d, %ld\n", pid, flag, resource_id, lock_type, timeout);
			process_unlock(pid, resource_id);
			break;
		case MSG_FLAG_TRYLOCK:
			printf("Processing trylock %d, %d, %d, %d, %ld\n", pid, flag, resource_id, lock_type, timeout);
			process_trylock(pid, resource_id, lock_type);
			break;
		default:
			fprintf(stderr, "unsupported flag\n");
		}
		pthread_mutex_unlock(&global_mutex);
	}
}

void process_read_buff(char* buff, pid_t* pid, char* flag, int* resource_id, int* lock_type, long* timeout)
{
	memcpy(pid, buff, sizeof(pid_t));
	memcpy(flag, buff + sizeof(pid_t), sizeof(char));
	memcpy(resource_id, buff + sizeof(pid_t) + sizeof(char), sizeof(int));
	if(*flag == MSG_FLAG_LOCK || *flag == MSG_FLAG_TRYLOCK)
	{
		memcpy(lock_type, buff + sizeof(pid_t) + sizeof(char) + sizeof(int), sizeof(int));
	}
	if(*flag == MSG_FLAG_LOCK)
	{
		memcpy(timeout, buff + sizeof(pid_t) + sizeof(char) + 2*sizeof(int), sizeof(long));
	}
}

void process_lock(pid_t pid, int resource_id, int lock_type, long timeout)
{
	bool non_block = timeout == -1;
	bool immediate = dlm_manager->lock(pid, resource_id, lock_type, non_block);
	if(non_block || immediate)
	{
		write_response(pid, immediate ? DLM_RETURN_OK : DLM_RETURN_FAIL);
	}
	else if(timeout > 0)
	{
		pthread_t thread; //FIXME: to chyba nie powinna być zmienna na stosie, ale dziala
		long *data = new long[3];
		data[0] = pid,
		data[1] = resource_id;
		data[2] = timeout;
		pthread_create(&thread, NULL, timeout_thread, data);
	}
}

void process_unlock(pid_t pid, int resource_id)
{
	write_response(pid, DLM_RETURN_OK);
	std::vector<pid_t> next_holders = dlm_manager->unlock(pid, resource_id);

	if(next_holders.size() > 0)
	{
		for(int i = 0; i < next_holders.size(); ++i)
		{
			pid_t pid = next_holders[i];
			ignore_timout_set->insert(pid);
			write_response(pid, DLM_RETURN_OK);
		}
	}
}

void process_trylock(pid_t pid, int resource_id, int lock_type)
{
	bool result = dlm_manager->try_lock(resource_id, lock_type);
	write_response(pid, result ? DLM_RETURN_OK : DLM_RETURN_FAIL);
}

void process_timeout(pid_t pid, int resource_id)
{
	pthread_mutex_lock(&global_mutex);
	
	//check if pid already obtained lock
	if(ignore_timout_set->erase(pid) > 0) {
		return;
	}

	dlm_manager->timeout(pid, resource_id);
	write_response(pid, DLM_RETURN_FAIL);

	pthread_mutex_unlock(&global_mutex);
}

void write_response(pid_t pid, char return_value)
{
	int client_fd;
	if((client_fd = get_client_descriptor(pid)) > 0)
	{
		write(client_fd, (void *) &return_value, (size_t) sizeof(char));
	}
	else
	{
		fprintf(stderr, "pid %d not found\n", pid);
	}
}

int get_client_descriptor(pid_t pid)
{
	if(client_descriptors_map->find(pid) == client_descriptors_map->end())
	{
		return -1;
	}
	return (*client_descriptors_map)[pid];
}

void clean_up_client_init()
{
	std::map<pid_t, int>::iterator it = client_descriptors_map->begin();
	while(it != client_descriptors_map->end())
	{
		close(it->second);
		++it;
	}

	delete client_descriptors_map;
	client_descriptors_map = NULL;
}

int send_msg(char msg_type, pid_t pid, int resource_id, int lock_type, long timeout)
{	
	int client_fd;
	if((client_fd = get_client_descriptor(pid)) <= 0)
	{
		return DLM_RETURN_FAIL;
	}

	char msg[CLIENT_SERVER_PACKET_LENGTH];
	char flag = msg_type;

	memcpy(msg, &pid, sizeof(pid_t));
	memcpy(msg + sizeof(pid_t), &flag, sizeof(char));
	memcpy(msg + sizeof(pid_t) + sizeof(char), &resource_id, sizeof(int));
	memcpy(msg + sizeof(pid_t) + sizeof(char) + sizeof(int), &lock_type, sizeof(int));
	memcpy(msg + sizeof(pid_t) + sizeof(char) + 2*sizeof(int), &timeout, sizeof(long));

	write(client_fd, (void *) msg, (size_t) sizeof(msg));

	return DLM_RETURN_OK;
}

int read_msg(char *msg_type, int* resource_id, int* lock_type, long* timeout)
{
	char buff[CLIENT_SERVER_PACKET_LENGTH];
	pid_t pid;

	read(client_pipe, buff, CLIENT_SERVER_PACKET_LENGTH);
	process_read_buff(buff, &pid, msg_type, resource_id, lock_type, timeout);

	return DLM_RETURN_OK;
}

std::vector<pid_t> get_client_pid_vector()
{
	
	std::map<pid_t, int>::iterator it = client_descriptors_map->begin();
	if(pid_vector.empty())
	while(it != client_descriptors_map->end())
	{
		pid_vector.push_back(it->first);
		++it;
	}
	
	return pid_vector;
}

void DLM_cleanup()
{
	close(common_pipe);
	close(client_pipe);
}

void *timeout_thread(void *ptr)
{
	long *a = (long*) ptr;
	pid_t pid = (pid_t) a[0];
	int resource_id = (int) a[1];
	long timeout = a[2];
	delete a;

	sleep(timeout);

	process_timeout(pid, resource_id);
}

int DLM_lock(int resource_id, int lock_type, long timeout)
{
	//lock packet
	//pid | flag | resource_id | lock_type | timeout
	char msg[CLIENT_SERVER_PACKET_LENGTH];
	pid_t pid =  getpid();
	char flag = MSG_FLAG_LOCK;

	memcpy(msg, &pid, sizeof(pid_t));
	memcpy(msg + sizeof(pid_t), &flag, sizeof(char));
	memcpy(msg + sizeof(pid_t) + sizeof(char), &resource_id, sizeof(int));
	memcpy(msg + sizeof(pid_t) + sizeof(char) + sizeof(int), &lock_type, sizeof(int));
	memcpy(msg + sizeof(pid_t) + sizeof(char) + 2*sizeof(int), &timeout, sizeof(long));

	write(common_pipe, (void *) msg, (size_t) sizeof(msg));

	//waiting for response from server
	read(client_pipe, (void *) msg, (size_t) sizeof(char));
	int return_value;
	memcpy(&return_value, msg, (size_t) sizeof(char));
	return return_value;
}

int DLM_unlock(int resource_id)
{
	//unlock packet
	//pid | flag | resource_id | don't care
	char msg[CLIENT_SERVER_PACKET_LENGTH];
	pid_t pid =  getpid();
	char flag = MSG_FLAG_UNLOCK;

	memcpy(msg, &pid, sizeof(pid_t));
	memcpy(msg + sizeof(pid_t), &flag, sizeof(char));
	memcpy(msg + sizeof(pid_t) + sizeof(char), &resource_id, sizeof(int));

	write(common_pipe, (void *) msg, (size_t) sizeof(msg));

	//waiting for response from server
	read(client_pipe, (void *) msg, (size_t) sizeof(char));
	int return_value;
	memcpy(&return_value, msg, (size_t) sizeof(char));
	return return_value;
}

int DLM_trylock(int resource_id, int lock_type)
{
	//trylock packet
	//pid | flag | resource_id | lock_type | don't care
	char msg[CLIENT_SERVER_PACKET_LENGTH];
	pid_t pid =  getpid();
	char flag = MSG_FLAG_TRYLOCK;

	memcpy(msg, &pid, sizeof(pid_t));
	memcpy(msg + sizeof(pid_t), &flag, sizeof(char));
	memcpy(msg + sizeof(pid_t) + sizeof(char), &resource_id, sizeof(int));
	memcpy(msg + sizeof(pid_t) + sizeof(char) + sizeof(int), &lock_type, sizeof(int));

	write(common_pipe, (void *) msg, (size_t) sizeof(msg));

	//waiting for response from server
	read(client_pipe, (void *) msg, (size_t) sizeof(char));
	int return_value;
	memcpy(&return_value, msg, (size_t) sizeof(char));
	return return_value;
}

void critical(const char * msg)
{
	perror(msg);
}

